package fallingGame;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JOptionPane;

/**
 * <p>
 * Title: GameModel
 * </p>
 * <p>
 * Description: CS 343 Course Project GameModel is the model for the Model View
 * Controller (MVC). It knows nothing about the GUI and handles all the required
 * calculations.
 * </p>
 * 
 * @author Nathanial Howard, Joe Costello, Dustin Schwingle, Jared Potter
 * @email nathanial.howard@my.uwrf.edu, anthony.costello@my.uwrf.edu,
 *        dustin.schwingle@my.uwrf.edu, jared.potter@my.uwrf.edu
 * @date December 01, 2011
 * @team Group 2
 */
public class GameModel extends Thread {

	// physics items
	private float GRAVITY = 0.25f;
	private float terminal_velocity = 2.5f;

	// input
	private GameInput input;

	// score timer
	private PlayerScore scoreTimer;

	// sound
	private GameSound sound;

	private boolean gameOver;
	private boolean runForeverModel;
	private int score;

	// your character
	private Hero hero;
	// enemy objects
	private List<Brick> bricks;
	private Star star;

	/**
	 * GameModel default constructor - initialize data members and start a new
	 * game
	 * 
	 * @ensure a new game will be started
	 */
	public GameModel() {

		sound = new GameSound();
		runForeverModel = true;

		newGame();
	}

	/**
	 * newGame - Start a new game and reset the hero's position, the bricks, the
	 * score, and the terminal velocity
	 * 
	 * @ensure a new game will be started
	 */
	public void newGame() {

		gameOver = false;

		// reset input so movement from the previous game is not carried over
		input = new GameInput();

		// reset terminal velocity
		terminal_velocity = 2.5f;

		// reset score
		scoreTimer = new PlayerScore();

		/*
		 * Reset the position of the hero, bricks, and star
		 */
		// hero:
		hero = new Hero();
		hero.setHeight(56);
		hero.setWidth(18);
		hero.setX(400);
		hero.setY(416);
		hero.setActive(true);

		// enemies:
		bricks = new ArrayList<Brick>();

		// add bricks
		for (int i = 0; i < 15; i++) {
			Brick b;
			b = new Brick();
			b.spawn();

			// spawn them 31 pixels apart
			// this will place 15 bricks evenly across the map and not overlap.
			b.setY(-(i * 31) - b.getHeight());
			bricks.add(b);
		}

		// goal object
		star = new Star();
		star.spawn();
	}

	/**
	 * checkInput - checks if the user is moving the hero left, right, or
	 * jumping
	 * 
	 * @ensure the hero's position, direction, and velocity will be updated
	 */
	public void checkInput() {

		// movement
		if (input.isLeft() == input.isRight()) {
			hero.setX_vel(0f);
		} else if (input.isLeft()) {
			hero.setX_vel(-3.5f);
			hero.setFacingRight(false);
		} else if (input.isRight()) {
			hero.setX_vel(3.5f);
			hero.setFacingRight(true);
		}

		// jumping..check for input and give the character a "push"
		if (input.isUpKey() && !hero.isJumping()) {
			hero.setJumping(true);
			hero.setY_vel(-6.0f);
		}
	}

	/**
	 * run - start a new thread that executes the physics, checks user input,
	 * increases the terminal velocity, updates the score, and checks collision
	 * 
	 * @ensure a new thread will be started
	 */
	public void run() {

		// make it so the game doesn't start until a user hits "New Game"
		gameOver = true;

		// run the thread forever
		while (runForeverModel) {
			// update model only if the game is not over
			while (!gameOver) {
				physics();
				checkInput();
				terminal_velocity += 0.00125;
				score = scoreTimer.getTask().getPlayerScore();
				checkBricks();
				checkStar();
				try {
					Thread.sleep(15);
				} catch (InterruptedException e) {
				}
			}
			try {
				Thread.sleep(15);
			} catch (InterruptedException e) {
			}
		}
	}

	/**
	 * physics - updates the gravity, velocity, and direction for the hero,
	 * bricks, and the star
	 * 
	 * @ensure physics of the game will be updated
	 */
	public void physics() {

		// gravity and movement for hero
		hero.setY_vel(hero.getY_vel() + GRAVITY);
		hero.setX(hero.getX() + hero.getX_vel());
		hero.setY(hero.getY() + hero.getY_vel());

		// collision detection
		if (hero.getY() > 416) {
			hero.setY(416);
			hero.setY_vel(0f);
			hero.setJumping(false);
		}

		if (hero.getX() > 586) {
			hero.setX(586);
			hero.setX_vel(0f);
		}
		if (hero.getX() < 187) {
			hero.setX(187);
			hero.setX_vel(0f);
		}

		// gravity and movement for bricks
		for (Brick b : bricks) {

			// get pulled down by gravity
			if (b.getY_vel() < terminal_velocity)
				b.setY_vel(b.getY_vel() + GRAVITY);
			else
				b.setY_vel(terminal_velocity);

			// move
			b.setX(b.getX() + b.getX_vel());
			b.setY(b.getY() + b.getY_vel());

		}

		// get pulled down by gravity
		if (star.getY_vel() < terminal_velocity)
			star.setY_vel(star.getY_vel() + GRAVITY);
		else
			star.setY_vel(terminal_velocity);

		// move
		star.setX(star.getX() + star.getX_vel());
		star.setY(star.getY() + star.getY_vel());
	}

	/**
	 * checkBricks - collision detection for the bricks. If a brick hits the
	 * ground it will respawn at the top and if the player hits a brick the game
	 * will end, a sound will be played, and a window will appear that allows
	 * the user to save their score
	 * 
	 * @ensure collision will be detected for bricks
	 */
	public void checkBricks() {
		for (Brick b : bricks) {

			// respawn bricks when they touch the ground
			if (b.getY() > 464) {
				b.spawn();
			}

			// check collisions
			if (hero.getX() + hero.getWidth() > b.getX()
					&& hero.getX() < b.getX() + b.getWidth()
					&& hero.getY() + hero.getHeight() > b.getY()
					&& hero.getY() < b.getY() + b.getHeight()) {
				gameOver = true;
				/*
				 * stop the score, play a sound, and show a JOptionPane window
				 * to inform the user that the game is over and give them the
				 * option to save their score
				 */
				scoreTimer.getTimer().cancel();
				sound.playHit();
				String name = JOptionPane.showInputDialog(null,
						"Game Over\n\nYour score was " + score
								+ "!\n\nEnter your name to save your score",
						"Game Over", JOptionPane.PLAIN_MESSAGE);
				if (name != null && !name.isEmpty())
					new HighScore().saveHighScore(name, score);
			}
		}
	}

	/**
	 * checkStars - collision detection for the star. If a player collects a
	 * star hits then 100 points will be added to the score, a sound will be
	 * played, and the star will respawn at the top
	 * 
	 * @ensure collision will be detected for a star
	 */
	public void checkStar() {
		// bounce
		if (star.getY() > 456) {
			star.setY(456);
			star.setY_vel(-star.getY_vel());
		}

		// check collisions
		if (hero.getX() + hero.getWidth() > star.getX()
				&& hero.getX() < star.getX() + star.getWidth()
				&& hero.getY() + hero.getHeight() > star.getY()
				&& hero.getY() < star.getY() + star.getHeight()) {

			// add some score
			star.spawn();
			scoreTimer.add(100);
			sound.playHit();
		}

		// respawn the star if it goes out of bounds
		if (star.getX() > 586 || star.getX() < 187) {
			star.spawn();
		}

	}

	/**
	 * getHero() getter for hero
	 * 
	 * @return hero
	 * @ensure hero is returned
	 */
	public Hero getHero() {
		return hero;
	}

	/**
	 * getBricks() getter for bricks
	 * 
	 * @return list of bricks
	 */
	public List<Brick> getBricks() {
		return bricks;
	}

	/**
	 * getStar() getter for star
	 * 
	 * @return star
	 * @ensure star is returned
	 */
	public Star getStar() {
		return star;
	}

	/**
	 * getInput - getter for GameInput
	 * 
	 * @return input for the game
	 * @ensure input will be returned
	 */
	public GameInput getInput() {
		return input;
	}

	/**
	 * getSound - getter for the GameSound
	 * 
	 * @return sound for the game
	 * @ensure sound will be returned
	 */
	public GameSound getSound() {
		return sound;
	}

	/**
	 * isGameOver - getter for gameOver
	 * 
	 * @return is the game over
	 * @ensure gameOver will be returned
	 */
	public boolean isGameOver() {
		return gameOver;
	}

	/**
	 * getScore - getter for the score
	 * 
	 * @return the game score
	 * @ensure score will be returned
	 */
	public int getScore() {
		return score;
	}

	/**
	 * setRunForeverModel
	 * 
	 * setter for runForeverModel
	 * 
	 * @param runForeverModel
	 * @ensure this.runForeverModel = runForeverModel
	 */
	public void setRunForeverModel(boolean runForeverModel) {
		this.runForeverModel = runForeverModel;
	}
}
